/**
 * 
 */
package com.vision.core.sv.serv;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.SQLException;

import javax.servlet.http.HttpServletRequest;

import net.sf.gilead.core.PersistentBeanManager;
import net.sf.gilead.core.hibernate.HibernateUtil;
import net.sf.gilead.core.serialization.GwtProxySerialization;
import net.sf.gilead.core.store.stateless.StatelessProxyStore;
import net.sf.gilead.gwt.GileadRPCHelper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spring4gwt.server.SpringGwtRemoteServiceServlet;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.server.rpc.RPC;
import com.google.gwt.user.server.rpc.RPCRequest;
import com.vision.core.cm.exception.RpcException;
import com.vision.core.sv.db.CustomSessionFactoryBean;
import com.vision.core.sv.util.DB;

/**
 * @author Mark
 *
 */
public class SpringGwtServlet extends SpringGwtRemoteServiceServlet {
	
	private static final long serialVersionUID = 1L;
	
	// Source code of SpringGwtRemoteServiceServlet ****************************
	
	private static final Logger log = LoggerFactory.getLogger(SpringGwtServlet.class);
	
	private PersistentBeanManager beanManager;

    @Override
    public void init() {
    	log.debug("Spring GWT service exporter deployed");
        
        // Initialize the Gilead ClassLoader
        GileadRPCHelper.initClassLoader();
		/* Start of boilerplate code for Gilead. */
		HibernateUtil gileadHibernateUtil = HibernateUtil.getInstance();
	    gileadHibernateUtil.setSessionFactory( CustomSessionFactoryBean.getFactory() );

	    beanManager = new PersistentBeanManager();
	    beanManager.setPersistenceUtil(gileadHibernateUtil);
	    
	    StatelessProxyStore sps = new StatelessProxyStore();
	    sps.setProxySerializer(new GwtProxySerialization());
	    beanManager.setProxyStore(sps);
	    /* End of boilerplate code for Gilead. */
    }
    
    @Override
    public String processCall(String payload) throws SerializationException {
    	RPCRequest rpcRequest = null;
    	
    	try {
	    	Object handler = getBean(getThreadLocalRequest());
	    	rpcRequest = RPC.decodeRequest(payload, handler.getClass(), this);
	    	
//	    	// Gilead-based services (converts Hibernate objects into GWT-understandable classes)
//	    	if (handler instanceof PersistentRemoteService) {
//		    	return invokeGileadService((PersistentRemoteService) handler, rpcRequest);
//	    	}
//	    	
//	    	// Standard GWT RPC services
//    		String result = invokeStandardGwtService(handler, rpcRequest);
//    		return result;
	    	
	    	String result = invokeGileadService(handler, beanManager, rpcRequest);
	    	
	    	return result;
    	} catch (RpcException e) {
    		log.error(e.getMessage(), e);
    		return RPC.encodeResponseForFailure(rpcRequest.getMethod(), e, rpcRequest.getSerializationPolicy());
    	} catch (InvocationTargetException e) {
    		if (e.getTargetException() instanceof RpcException) {
    			RpcException rpcE = (RpcException) e.getTargetException();
				return RPC.encodeResponseForFailure(rpcRequest.getMethod(), rpcE, rpcRequest.getSerializationPolicy());
    		} else {
	    		SQLException sqle = DB.getSQLException(e.getTargetException());
	    		if (sqle != null)
	    			log.error(sqle.getMessage(), sqle);
	    		else
	    			log.error(e.getMessage(), e);
	    		
	    		return RPC.encodeResponseForFailure(null, e);
    		}
    	} catch (IllegalAccessException e) {
    		log.error("An IllegalAccessException was thrown while processing this call.", e);
    		return RPC.encodeResponseForFailure(null, e);
    	} catch (IncompatibleRemoteServiceException e) {
    		log.error("An IncompatibleRemoteServiceException was thrown while processing this call.", e);
    		return RPC.encodeResponseForFailure(null, e);
    	}
    }
    
//    /**
//     * 
//     * @param service
//     * @param rpcRequest
//     * @return
//     * @throws IllegalArgumentException
//     * @throws IllegalAccessException
//     * @throws InvocationTargetException
//     * @throws SerializationException
//     * @throws RpcException 
//     */
//    private String invokeGileadService(PersistentRemoteService service, RPCRequest rpcRequest) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SerializationException, RpcException  {
//    	return invokeGileadService(service, service.getBeanManager(), rpcRequest);
//    }
    
    private String invokeGileadService(Object service, PersistentBeanManager beanManager, RPCRequest rpcRequest) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SerializationException, RpcException  {
    	// Code is just copied from PersistentRemoteService from Gilead
    	Method method = rpcRequest.getMethod();
    	
    	Object returnValue = method.invoke(service, rpcRequest.getParameters());
    	returnValue = GileadRPCHelper.parseReturnValue(returnValue, beanManager);
    	
    	String rpcResult = RPC.encodeResponseForSuccess(method, returnValue, rpcRequest.getSerializationPolicy());
    	return rpcResult;
    }
    
//    /**
//     * 
//     * @param handler
//     * @param rpcRequest
//     * @return
//     * @throws SerializationException
//     */
//    private String invokeStandardGwtService(Object handler, RPCRequest rpcRequest) throws SerializationException, RpcException {
//    	onAfterRequestDeserialized(rpcRequest);
//        // TODO Handle AccessDeniedExceptions thrown by Spring security and properly send exception to client
//        return RPC.invokeAndEncodeResponse(handler, rpcRequest.getMethod(), rpcRequest.getParameters(), rpcRequest.getSerializationPolicy());
//    }

    /**
     * Determine Spring bean to handle request based on request URL, e.g. a
     * request ending in /myService will be handled by bean with name
     * "myService".
     * 
     * @param request
     * @return handler bean
     */
    protected Object getBean(HttpServletRequest request) {
        String service = getService(request);
        Object bean = getBean(service);
        if (!(bean instanceof RemoteService)) {
            throw new IllegalArgumentException("Spring bean is not a GWT RemoteService: " + service + " (" + bean + ")");
        }
        
        log.trace("Bean for service {} is {}.", service, bean);
        
        return bean;
    }

    /**
     * Parse the service name from the request URL.
     * 
     * @param request
     * @return bean name
     */
    protected String getService(HttpServletRequest request) {
        String url = request.getRequestURI();
        String service = url.substring(url.lastIndexOf("/") + 1);
        
        log.trace("Service for URL {} is {}.", url, service);
        
        return service;
    }

    /**
     * Look up a spring bean with the specified name in the current web
     * application context.
     * 
     * @param name
     *            bean name
     * @return the bean
     */
    protected Object getBean(String name) {
        WebApplicationContext applicationContext = WebApplicationContextUtils
                        .getWebApplicationContext(getServletContext());
        if (applicationContext == null) {
            throw new IllegalStateException("No Spring web application context found");
        }
        
        if (!applicationContext.containsBean(name)) {
            throw new IllegalArgumentException("Spring bean not found: " + name);
        }
        return applicationContext.getBean(name);
    }
    
//  @Override
//  public String processCall(String payload) throws SerializationException {
//      try {
//          Object handler = getBean(getThreadLocalRequest());
//          
//          RPCRequest rpcRequest = RPC.decodeRequest(payload, handler.getClass(), this);
//          onAfterRequestDeserialized(rpcRequest);
//          
//          if (LOG.isDebugEnabled()) {
//          	LOG.debug("Invoking " + handler.getClass().getName() + "." + rpcRequest.getMethod().getName());
//          }
//          
//          // TODO Handle AccessDeniedExceptions thrown by Spring security and properly send exception to client
//          return RPC.invokeAndEncodeResponse(handler, rpcRequest.getMethod(), rpcRequest.getParameters(), rpcRequest.getSerializationPolicy());
//      } catch (IncompatibleRemoteServiceException ex) {
//          log("An IncompatibleRemoteServiceException was thrown while processing this call.", ex);
//          return RPC.encodeResponseForFailure(null, ex);
//      }
//  }

}
